/**********************************************************************
 *                                                                    *
 * tgt - Tiny Graphics Toolbox                                        *
 *                                                                    *
 * Copyright (C) 2006-2008 Visualization and Computer Graphics Group, *
 * Department of Computer Science, University of Muenster, Germany.   *
 * <http://viscg.uni-muenster.de>                                     *
 *                                                                    *
 * This file is part of the tgt library. This library is free         *
 * software; you can redistribute it and/or modify it under the terms *
 * of the GNU Lesser General Public License version 2.1 as published  *
 * by the Free Software Foundation.                                   *
 *                                                                    *
 * This library is distributed in the hope that it will be useful,    *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of     *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the       *
 * GNU Lesser General Public License for more details.                *
 *                                                                    *
 * You should have received a copy of the GNU Lesser General Public   *
 * License in the file "LICENSE.txt" along with this library.         *
 * If not, see <http://www.gnu.org/licenses/>.                        *
 *                                                                    *
 **********************************************************************/

#ifndef TGT_NAVIGATION_WALKNAVIGATION_H
#define TGT_NAVIGATION_WALKNAVIGATION_H

#include "tgt/config.h"
#include "tgt/vector.h"
#include "tgt/quaternion.h"
#include "tgt/event/keyevent.h"
#include "tgt/event/timeevent.h"
#include "tgt/event/mouseevent.h"
#include "tgt/glcanvas.h"
#include "tgt/navigation/navigation.h"
#include "tgt/camera.h"


using namespace tgt;


class WalkNavigation : public Navigation {
public:
    
    WalkNavigation(
        GLCanvas* canvas_,
        Camera* camera = NULL,
        int button1 = tgt::MouseEvent::MOUSE_BUTTON_1,
        int button2 = tgt::MouseEvent::MOUSE_BUTTON_2,
        const vec3& onGroundPlane = vec3(0,0,0), 
        double minHeight = 0.01,        
        double refHeight = 1.0,
        double walkSpeed = 0.1, 
        double runSpeed = 0.3,
        double verticalTime = 2.0,
        double horizontalAccelerationTime = 0.3,
        double verticalAccelerationTime = 0.3
    );
            

    double EPS;
    double MaxAltitude;

    
    double getLambda(double t) {
        double t2 = t*t;
        double t3 = t2*t;
        double lambda = -2.*t3 + 3 * t2;        
        return lambda;
    }

    // the result is v reduced to the part that is orthogonal to w.
    // w is assumed to be normalized
    inline vec3 makeOrthogonal(const vec3& v, const vec3& w) {
        return cross(w,cross(v,w));
    }

    template<typename T> inline T cropValue(T value, T min, T max) {
        if (value>max) value = max;
        else if (value<min) value = min;
        return value;
    }

    // returns the altitude angle in degrees
    //double getAltitude(const vec3& dir, const vec3& up) {
	//	return 90. - rad2deg * acos(dot(dir, up));
    //}            


    double getSensitivity() const;
        //!< Returns the current sensitivity value.
    void setSensitivity(double sensitivity = 0.05);
        /*!< The sensitivity value controls the effect of the mouse to the
             viewing direction. It must be positive. */
       
    vec3 getGroundPlaneVector() const;
        //!< Returns the ground plane vector
    void setGroundPlaneVector(const vec3& onGroundPlane);
        //!< Sets the ground plane vector
       
    double getMinHeight() const;
        //!< Returns the minimum height
    void setMinHeight(double height);
        //!< Sets the minimum height

    double getReferenceHeight() const;
        //!< Returns the reference height
    void setReferenceHeight(double height);
        //!< Sets the reference height

    double getWalkSpeed() const;
        //!< Returns the walk speed 
    void setWalkSpeed(double s);
        //!< Sets the walk speed. 
    
    double getRunSpeed() const;
        //!< Returns the run speed
	void setRunSpeed(double s);
        //!< Sets the run speed. (see constructor comment)
    
    double getVerticalTime() const; 
        //!< Returns the time needed to halve/double the camera height
    void setVerticalTime(double time);
        //!< Sets the time needed to halve/double the camera height

    double getHorizontalAccelerationTime() const;
        //!< Returns the acceleration time for horizontal movement
    void setHorizontalAccelerationTime(double acceleration);
        //!< Sets the acceleration time for horizontal movement

    double getVerticalAccelerationTime() const;
        //!< Return the acceleration time for vertical movement
    void setVerticalAccelerationTime(double acceleration);
        //!< Sets the accleration time for vertical movement
	
    bool isRunning() const;
        /*!< Each WalkNavigation stores two speed values, one value for walking and
             one usually larger value for running. By default it can be switched
            between walking and running by the space key. By isRunning() one can
            check, whether the run-mode is active. */
        		
	void setKeyTimeout(unsigned int t); 
	    /*!< Some simpler GUI toolkits do not send a key-release message, but send multiple messages
		     for key repeat. In this case a timeout value must be set. With each key-pressed event
            a countdown is re-initialized with a timeout value. If the countdown reaches zero,
            an internal key-release event is invoked. The timeout value is specified in fiftieth 
            parts of a second. E.g. if you set t=5 and press the forward key very shortly, the forward movement stops
		    past a tenth part of a second. t is expected to be a positive integer between 0 and 1000.
            The key timeout is disabled by passing t=0 (default value). */

	void setKeys(unsigned int front, unsigned int back, unsigned int left, unsigned int right,
			unsigned int up, unsigned int down, unsigned int toggleSpeed, unsigned int turnAround);
	    //!< Defines control keys. With the toogleSpeed key you can change between walk speed and run speed.

	void enableKeys();
	void disableKeys();
	    /*!< If you want to use the WalkNavigation only to look around, but to move via other
		     navigation metaphors, you can disable the control keys. */

	void enableMouseButtonAcceleration(int button = MouseEvent::MOUSE_BUTTON_3);
	/*!< By default, all movement is controlled by keys. Alternatively you can invoke
	     forward acceleration by pressing a mouse key. By default, this option is disabled. */
	void disableMouseButtonAcceleration();
	/*!< Disables acceleration by mouse button. */
	
    double getHorizontalScaling() const;        
    double getVerticalScaling() const;
    void setScaling(double horizontal = 1.0, double vertical = 1.0);
        /*!< The behavior of a WalkNavigation is specified by several
            parameters that were quite inconvenient to specify explicitly. 
            By default all parameters are chosen in a way that the Navigation behaves 
            "reasonable" in a scene that is scaled to the [0,1]-cube. If you
            want to scale your scene you can easily scale the navigation appropriately,
            so that the behavior of the navigation keeps the same. 
            Note: The setScaling() method does not change the parameters directly, 
                  e.g. the value return by getRunSpeed() keeps unaffected by
                  the scaling. */

	virtual void onEvent(Event*);
        /*!< Event handling methods. */
    
private:

    GLCanvas* canvas__;
    Camera* camera_;

    // configuration variables (cannot changed via mouse or keyboard)
    double walkSpeed_;
    double runSpeed_;
    double verticalTime_;

    double minHeight_;
    double refHeight_;

    double horizontalAccelerationTime_;
    double verticalAccelerationTime_;

    double horizontalScaling_;
    double verticalScaling_;

    double sensitivity_;

    vec3 onGroundPlane_;

    int keyTimeout_;

    // control keys
    unsigned int frontKey_;
    unsigned int backKey_;
    unsigned int rightKey_;
    unsigned int leftKey_;
    unsigned int upKey_;
    unsigned int downKey_;
    unsigned int runKey_;
    unsigned int turnAroundKey_;
    bool enableKeys_;

    // state variables    
    bool running_;

    bool forwardPressed_;   // key states
    bool backwardPressed_;
    bool leftPressed_;
    bool rightPressed_;
    bool upPressed_;
    bool downPressed_;
    bool turnKeyPressed_;

    bool turnButtonPressed_;
    bool moveButtonPressed_;

    double currentForwardSpeed_;    // can be negative
    double currentSidewardSpeed_;   // positive values for movement to the right
    double verticalSpeedAmount_;    // controls the vertical acceleration. 
                                            
    // key timeouts
    int forwardTimeout_;
    int sidewardTimeout_;
    int upDownTimeout_;

    // other       		        
    int lastX_;
    int lastY_;
    double dx_;
    double dy_;    
    int turnAroundSteps_; // countdown for the turn-around rotation. 

    vec3 from_;
    vec3 to_;
    vec3 y_up_;

    bool s_constr_;

    int button1_;
    int button2_;	
    int mouseButtonAccelerationButton_;
    bool enableMouseButtonAcceleration_;
    bool accelerationButtonPressed_;

    double elapsedTime_;
};

inline double WalkNavigation::getSensitivity() const { return sensitivity_; }
inline vec3 WalkNavigation::getGroundPlaneVector() const { return onGroundPlane_; }
inline double WalkNavigation::getMinHeight() const { return minHeight_; }
inline double WalkNavigation::getReferenceHeight() const { return refHeight_; }
inline double WalkNavigation::getWalkSpeed() const { return walkSpeed_; }
inline double WalkNavigation::getRunSpeed() const { return runSpeed_; }
inline double WalkNavigation::getVerticalTime() const { return verticalTime_; }
inline double WalkNavigation::getHorizontalAccelerationTime() const { return horizontalAccelerationTime_; }
inline double WalkNavigation::getVerticalAccelerationTime() const { return verticalAccelerationTime_; }
inline bool WalkNavigation::isRunning() const { return running_; }
inline double WalkNavigation::getHorizontalScaling() const { return horizontalScaling_; }
inline double WalkNavigation::getVerticalScaling() const { return verticalScaling_; }

#endif // TGT_NAVIGATION_WALKNAVIGATION_H

